#include "fixture.hh"
#include "color.hh"
#include "testcase.hh"
#ifndef DISABLE_TRACE
#include "detail/stack_trace_windows.hh"
#endif // DISABLE_TRACE
#include <algorithm>
#include <exception>
#include <iomanip>
#include <iostream>
#include <typeinfo>

namespace k {
namespace test {

Fixture::Fixture(const std::string &name)
    : name_(name), run_(true), passed_(0), failed_(0) {}

Fixture::~Fixture() {
  for (auto testcase : caseList_) {
    delete testcase;
  }
}

void Fixture::setup() {
  if (setup_) {
    setup_();
  }
}

void Fixture::tearDown() {
  if (tearDown_) {
    tearDown_();
  }
}

void Fixture::disableAllTestCase() {
  for (auto testcase : caseList_) {
    testcase->disable();
  }
}

const Fixture::TestcaseList &Fixture::getAllCase() { return caseList_; }

bool Fixture::onSetup(FixtureSetupMethod method) {
  setup_ = method;
  return true;
}

bool Fixture::onTearDown(FixtureTearDownMethod method) {
  tearDown_ = method;
  return true;
}

const std::string &Fixture::getName() const { return name_; }

bool Fixture::addTestcase(Testcase *testcase) {
  caseList_.push_back(testcase);
  return true;
}

Testcase *Fixture::getTestcase(const std::string &name) {
  for (auto testcase : caseList_) {
    if (testcase->getName() == name) {
      return testcase;
    }
  }
  return nullptr;
}

void Fixture::disableTestcase(const std::string &name) {
  auto it = std::find_if(
      caseList_.begin(), caseList_.end(),
      [&](const Testcase *testcase) { return (testcase->getName() == name); });
  if (it == caseList_.end()) {
    return;
  }
  (*it)->disable();
}

void Fixture::enableTestcase(const std::string &name) {
  auto it = std::find_if(
      caseList_.begin(), caseList_.end(),
      [&](const Testcase *testcase) { return (testcase->getName() == name); });
  if (it == caseList_.end()) {
    return;
  }
  (*it)->enable();
}

#ifdef WIN32

#ifndef DISABLE_TRACE
std::string global_stack_info_string;
#endif // #ifndef DISABLE_TRACE

LONG set_exception_context(void *except_info) {
#ifndef DISABLE_TRACE
  global_stack_info_string =
      kratos::util::StackTrace::get_unhandled_exception_stack_frame_info(
          except_info);
#endif // DISABLE_TRACE
  return EXCEPTION_EXECUTE_HANDLER;
}

#endif // WIN32

void runTestCaseSafe(Testcase *testcase) {
#ifdef WIN32
#ifndef DISABLE_TRACE
  __try {
    testcase->run();
  } __except (set_exception_context(GetExceptionInformation())) {
    std::cout << global_stack_info_string << std::endl;
  }
#else
  testcase->run();
#endif // DISABLE_TRACE
#else
  testcase->run();
#endif // WIN32
}

void Fixture::runAllTestcase() {
  std::cout << "Run Suite [ " << green << name_ << black << " ]" << std::endl;
  std::size_t maxLength = 0;
  for (auto testcase : caseList_) {
    if (maxLength < testcase->getName().size()) {
      maxLength = testcase->getName().size();
    }
  }
  std::size_t pass = 0;
  std::size_t fail = 0;
  std::size_t disableCount = 0;
  std::size_t exception = 0;
  for (auto testcase : caseList_) {
    if (testcase->isEnable()) {
      std::cout << "Run [ " << green << std::left << std::setw(maxLength)
                << testcase->getName() << black << " ]";
      try {
        runTestCaseSafe(testcase);
        if (testcase->isSuccess()) {
          pass++;
          std::cout << " [" << green << std::left << std::setw(11) << " PASS "
                    << black << "]" << std::endl;
        } else {
          fail++;
          std::cout << " [" << red << std::left << std::setw(11) << " FAIL "
                    << black << "]" << std::endl;
          for (auto &error : testcase->getErrorList()) {
            std::cout << red << "  > " << black << error << std::endl;
          }
        }
      } catch (std::exception &e) {
        fail++;
        exception++;
        std::cout << " [ " << red << "EXCEPTION" << black << " ]" << std::endl;
        std::cout << red << "  > " << black << "[ " << green << typeid(e).name()
                  << black << " ]" << std::endl;
        std::cout << "    > " << e.what() << std::endl;
      } catch (...) {
        fail++;
        exception++;
        std::cout << " [ " << red << "EXCEPTION" << black << " ]" << std::endl;
      }
      testcase->clearError();
    } else {
      disableCount++;
      // std::cout << "Run [ " << std::left << std::setw(maxLength) <<
      // testcase->getName() << " ]"; std::cout << " [" << std::left <<
      // std::setw(11) << " DISABLED " << "]" << std::endl;
    }
  }
  std::cout << "    [ " << std::left << std::setw(maxLength) << pass << " ] [ "
            << green << std::left << std::setw(10) << "PASS" << black << "] "
            << std::endl;
  if (fail) {
    std::cout << "    [ " << std::left << std::setw(maxLength) << fail
              << " ] [ " << red << std::left << std::setw(10) << "FAIL" << black
              << "] " << std::endl;
  }
  if (exception) {
    std::cout << "    [ " << std::left << std::setw(maxLength) << exception
              << " ] [ " << red << std::left << std::setw(10) << "EXCEPTION"
              << black << "] " << std::endl;
  }
  // if (disableCount) {
  //    std::cout << "    [ " << std::left << std::setw(maxLength) <<
  //    disableCount << " ] [ " << std::left << std::setw(10) << "DISABLE" << "]
  //    " << std::endl;
  //}
  passed_ += pass;
  failed_ += fail;
}

bool Fixture::isEnable() { return run_; }

void Fixture::disable() { run_ = false; }

void Fixture::enable() { run_ = true; }

std::size_t Fixture::getPassCases() { return passed_; }

std::size_t Fixture::getFailedCases() { return failed_; }

} // namespace test
} // namespace k
